/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.solr.search;

import org.apache.lucene.search.*;
import org.apache.lucene.index.IndexReader;

import java.io.IOException;

public class MissingStringLastComparatorSource extends FieldComparatorSource {
	public static final String bigString = "\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffffNULL_VAL";

	private final String missingValueProxy;

	public MissingStringLastComparatorSource() {
		this(bigString);
	}

	/**
	 * Creates a {@link FieldComparatorSource} that uses
	 * <tt>missingValueProxy</tt> as the value to return from
	 * ScoreDocComparator.sortValue() which is only used my multisearchers to
	 * determine how to collate results from their searchers.
	 * 
	 * @param missingValueProxy
	 *            The value returned when sortValue() is called for a document
	 *            missing the sort field. This value is *not* normally used for
	 *            sorting, but used to create
	 */
	public MissingStringLastComparatorSource(String missingValueProxy) {
		this.missingValueProxy = missingValueProxy;
	}

	@Override
	public FieldComparator newComparator(String fieldname, int numHits,
			int sortPos, boolean reversed) throws IOException {
		return new MissingLastOrdComparator(numHits, fieldname, sortPos,
				reversed, missingValueProxy);
	}

}

// Copied from Lucene and modified since the Lucene version couldn't
// be extended or have it's values accessed.
class MissingLastOrdComparator extends FieldComparator<String> {
	private static final int NULL_ORD = Integer.MAX_VALUE;
	private final String nullVal;

	private final int[] ords;
	private final String[] values;
	private final int[] readerGen;

	private int currentReaderGen = -1;
	private String[] lookup;
	private int[] order;
	private final String field;

	private int bottomSlot = -1;
	private int bottomOrd;
	private String bottomValue;
	private final boolean reversed;
	private final int sortPos;

	public MissingLastOrdComparator(int numHits, String field, int sortPos,
			boolean reversed, String nullVal) {
		ords = new int[numHits];
		values = new String[numHits];
		readerGen = new int[numHits];
		this.sortPos = sortPos;
		this.reversed = reversed;
		this.field = field;
		this.nullVal = nullVal;
	}

	@Override
	public int compare(int slot1, int slot2) {
		if (readerGen[slot1] == readerGen[slot2]) {
			int cmp = ords[slot1] - ords[slot2];
			if (cmp != 0) {
				return cmp;
			}
		}

		final String val1 = values[slot1];
		final String val2 = values[slot2];

		if (val1 == null) {
			if (val2 == null) {
				return 0;
			}
			return 1;
		} else if (val2 == null) {
			return -1;
		}
		return val1.compareTo(val2);
	}

	@Override
	public int compareValues(String first, String second) {
		if (first == null) {
			if (second == null) {
				return 0;
			} else {
				return 1;
			}
		} else if (second == null) {
			return -1;
		} else {
			return first.compareTo(second);
		}
	}

	@Override
	public int compareBottom(int doc) {
		assert bottomSlot != -1;
		int order = this.order[doc];
		int ord = (order == 0) ? NULL_ORD : order;
		final int cmp = bottomOrd - ord;
		if (cmp != 0) {
			return cmp;
		}

		final String val2 = lookup[order];

		// take care of the case where both vals are null
		if (bottomValue == val2)
			return 0;

		return bottomValue.compareTo(val2);
	}

	private void convert(int slot) {
		readerGen[slot] = currentReaderGen;
		int index = 0;
		String value = values[slot];
		if (value == null) {
			// should already be done
			// ords[slot] = NULL_ORD;
			return;
		}

		if (sortPos == 0 && bottomSlot != -1 && bottomSlot != slot) {
			// Since we are the primary sort, the entries in the
			// queue are bounded by bottomOrd:
			assert bottomOrd < lookup.length;
			if (reversed) {
				index = binarySearch(lookup, value, bottomOrd,
						lookup.length - 1);
			} else {
				index = binarySearch(lookup, value, 0, bottomOrd);
			}
		} else {
			// Full binary search
			index = binarySearch(lookup, value);
		}

		if (index < 0) {
			index = -index - 2;
		}
		ords[slot] = index;
	}

	@Override
	public void copy(int slot, int doc) {
		final int ord = order[doc];
		ords[slot] = ord == 0 ? NULL_ORD : ord;
		assert ord >= 0;
		values[slot] = lookup[ord];
		readerGen[slot] = currentReaderGen;
	}

	@Override
	public void setNextReader(IndexReader reader, int docBase)
			throws IOException {
		FieldCache.StringIndex currentReaderValues = FieldCache.DEFAULT
				.getStringIndex(reader, field);
		currentReaderGen++;
		order = currentReaderValues.order;
		lookup = currentReaderValues.lookup;
		assert lookup.length > 0;
		if (bottomSlot != -1) {
			convert(bottomSlot);
			bottomOrd = ords[bottomSlot];
		}
	}

	@Override
	public void setBottom(final int bottom) {
		bottomSlot = bottom;
		if (readerGen[bottom] != currentReaderGen) {
			convert(bottomSlot);
		}
		bottomOrd = ords[bottom];
		assert bottomOrd >= 0;
		// assert bottomOrd < lookup.length;
		bottomValue = values[bottom];
	}

	@Override
	public String value(int slot) {
		String v = values[slot];
		return v == null ? nullVal : v;
	}

	public String[] getValues() {
		return values;
	}

	public int getBottomSlot() {
		return bottomSlot;
	}

	public String getField() {
		return field;
	}
}
